package synchronicity.discovery;

import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;

import synchronicity.host.SchedulerHost;
import synchronicity.host.SchedulerHostSkeleton;
import synchronicity.host.SchedulerHostStub;

/**
 * A discovery mechanism using bluetooth
 * TODO Refactor all of this.
 */
public class BluetoothDiscovery implements Discovery, DiscoveryListener {
	protected static BluetoothDiscovery instance;

	protected DiscoveryAgent discoveryAgent;

	protected Vector devices;

	protected Vector records;

	protected volatile boolean inquiryStarted, inquiryComplete;

	protected volatile boolean serviceDiscoveryComplete;

	protected int discType;

	/** Optimization: keeps service search pattern. */
	protected UUID[] uuidSet;

	/** Optimization: keeps attributes list to be retrieved. */
	protected int[] attrSet;

	protected int[] searchIDs;

	BluetoothDiscovery() {

		// initialize some optimization variables
		uuidSet = new UUID[2];

		// ok, we are interesting in btspp services only
		uuidSet[0] = new UUID(0x1101);

		// and only known ones, that allows pictures
		uuidSet[1] = SchedulerHostSkeleton.SERVER_UUID;

		// we need an only service attribute actually
		attrSet = new int[1];

		attrSet[0] = SchedulerHostSkeleton.ATTRIBUTE_ID;

		inquiryStarted = false;
		serviceDiscoveryComplete = false;
		devices = new Vector();
		records = new Vector();
		try {
			LocalDevice localDevice = LocalDevice.getLocalDevice();
			discoveryAgent = localDevice.getDiscoveryAgent();
		} catch (BluetoothStateException btse) {
			btse.printStackTrace();
		}
	}

	public synchronized static BluetoothDiscovery getInstance() {
		if (instance == null) {
			instance = new BluetoothDiscovery();
		}
		return instance;
	}

	public SchedulerHost[] findOtherHosts() {
		if (!inquiryStarted) {
			inquiryStarted = true;
			try {
				discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
			} catch (BluetoothStateException e) {
				System.err.println("Can't start inquiry" + e);
			}
		}

		synchronized (this) {
			if (!inquiryComplete) {
				try {
					wait();
				} catch (InterruptedException e) {
				}
			}
		}
		if (devices.size() > 0) {
			searchIDs = new int[devices.size()];
			searchServices();
		}
		SchedulerHostStub[] results = new SchedulerHostStub[records.size()];
		for (int ii = 0; ii < records.size(); ii++) {
			ServiceRecord record = (ServiceRecord) records.elementAt(ii);
			results[ii] = new SchedulerHostStub(record);
		}
		System.out.println("Discovery complete. Found "+records.size()+" devices ");

		return results;
	}

	private void searchServices() {
		for (int ii = 0; ii < devices.size(); ii++) {
			RemoteDevice device = (RemoteDevice) devices.elementAt(ii);
			checkNulls(device);
			try {
				searchIDs[ii] = discoveryAgent.searchServices(attrSet, uuidSet,
						device, this);
			} catch (BluetoothStateException e) {
				e.printStackTrace();
				searchIDs[ii] = -1;
			}
		}

		System.out.println("Reached here 0 ");

		// We'll be notified when the last searchServices() is finished.
		synchronized (this) {
			if (!serviceDiscoveryComplete) {
				try {
					wait();
				} catch (InterruptedException e) {
				}
			}
		}
	}

	protected void checkNulls(RemoteDevice device) {
		for (int ii = 0; ii < uuidSet.length; ii++) {
			if (uuidSet[ii] == null) {
				System.out.println("uuidSet[" + ii + "] is null");
			}
		}
		if (device == null)
			System.out.println("Device is null");
	}

	public void deviceDiscovered(RemoteDevice arg0, DeviceClass arg1) {
		System.out.println("Device discovered " + arg0);
		if (devices.indexOf(arg0) == -1) {
			devices.addElement(arg0);
		}
	}

	public void inquiryCompleted(int arg0) {
		System.out.println("Inquiry completed");
		inquiryComplete = true;
		discType = arg0;
		synchronized (this) {
			notify();
		}
	}

	public void serviceSearchCompleted(int transID, int respCode) {
		System.out.println("Service search completed");

		int index = -1;
		for (int ii = 0; ii < searchIDs.length; ii++) {
			if (searchIDs[ii] == transID) {
				index = ii;
				break;
			}
		}
		if (index == -1) {
			System.err.println("Unexpected transaction index: " + transID);
			// FIXME: process the error case
		} else {
			searchIDs[index] = -1;
		}
		// Make sure it's the last one
		for (int ii = 0; ii < searchIDs.length; ii++) {
			if (searchIDs[ii] != -1)
				return;
		}
		synchronized (this) {
			serviceDiscoveryComplete = true;
			notifyAll();
		}
	}

	public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
		System.out.println("Service discovered!");
		for (int ii = 0; ii < arg1.length; ii++) {
			records.addElement(arg1[ii]);
		}
	}
}
